#!/usr/bin/python3
# -*- coding:utf-8 -*-
# Copyright (c) Huawei Technologies Co., Ltd. 2025.
# All rights reserved.
# Licensed under the CANN Open Software License Agreement Version 1.0.

"""
unwrap 算子结果验证脚本
功能：
    - 读取实际计算结果与 golden 结果
    - 计算绝对误差与相对误差
    - 验证误差是否在容忍范围内
    - 输出日志与退出码
"""

import os
import sys
import logging
import numpy as np
import warnings
import datetime

LOSS = 1e-5  # 绝对与相对误差容忍阈值
MINIMUM = 1e-10  # 避免除零的极小值

# 配置 logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

# float32 int32 uint8 int16  uint8 float16  uint16
def verify_result(real_result_path, golden_path, dtype=np.float32):
    """验证实际计算结果与golden结果是否一致"""
    warnings.filterwarnings('ignore', category=RuntimeWarning)

    # ==== 参数与文件检查 ====
    if not os.path.exists(real_result_path):
        logging.error(f"实际结果文件不存在: {real_result_path}")
        sys.exit(1)
    if not os.path.exists(golden_path):
        logging.error(f"Golden 文件不存在: {golden_path}")
        sys.exit(1)

    # ==== 读取数据 ====
    try:
        real_result = np.fromfile(real_result_path, dtype=dtype)
        golden = np.fromfile(golden_path, dtype=dtype)
    except Exception as e:
        logging.error(f"读取文件失败: {e}")
        sys.exit(1)

    if real_result.size != golden.size:
        logging.error(f"文件长度不一致! real={real_result.size}, golden={golden.size}")
        sys.exit(1)

    # if dtype == np.uint16:  #  BF16 是用 uint16 存储的 
    #     # 允许 1 ULP 误差
    #     diff = np.abs(real_result.astype(np.int32) - golden.astype(np.int32))
    #     ok_mask = diff <= 1

    #     fail_ratio = 1 - np.mean(ok_mask)

    #     print("[INFO] BF16 验证: 允许 1 ULP 误差")
    #     print("[INFO] BF16 Fail ratio:", fail_ratio)

    #     return fail_ratio
        
    # ==== 误差计算 ====
    abs_diff = np.abs(real_result - golden)
    denom = np.maximum(np.abs(real_result), np.abs(golden))

    abs_ok = abs_diff <= LOSS
    rel_ok = (abs_diff / (denom + MINIMUM)) <= LOSS

    # ==== 验证通过比例 ====
    abs_fail_ratio = np.mean(~abs_ok)
    rel_fail_ratio = np.mean(~rel_ok)

    # 读取input_x.bin中的数据（假设为int32类型）
    input_x_path = "input/input_x.bin"
    input_y_path = "input/input_y.bin"
    # 读取input_x_brc.bin中的数据（广播后的）
    input_x_brc_path = "input/input_x_brc.bin"
    input_y_brc_path = "input/input_y_brc.bin"

    try:
        # 读取二进制文件
        input_x = np.fromfile(input_x_path, dtype=np.int16)
        input_y = np.fromfile(input_y_path, dtype=dtype)
        # input_x = np.fromfile(input_x_brc_path, dtype=dtype)
        # input_y = np.fromfile(input_y_brc_path, dtype=dtype)
    except FileNotFoundError:
        logging.error(f"文件不存在：{input_x_path}")
        sys.exit(1)

    # 校验input_x长度与测试数据匹配
    if len(input_x) != len(rel_ok):
        logging.error(f"input_x长度不匹配：文件有{len(input_x)}个数据，测试数据有{len(rel_ok)}个")
        sys.exit(1)


    if abs_fail_ratio > LOSS and rel_fail_ratio > LOSS:

        # 1. 生成时间戳（格式：年-月-日_时-分-秒，避免特殊字符）
        timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")  # 例如：20251115_143025
        # 2. 配置错误日志的保存路径和带时间戳的文件名
        error_log_dir = "/home/ma-user/work/Errorlog"  # 指定目录，可自定义
        # 文件名格式：compare_errors_时间戳.log
        error_log_file = os.path.join(error_log_dir, f"compare_errors_{timestamp}.log")
        
        # 3. 强制创建目录（包括所有不存在的父目录），并捕获异常
        try:
            # exist_ok=True：目录已存在时不报错；exist_ok=False：目录存在时报错（默认）
            os.makedirs(error_log_dir, exist_ok=True)
            print(f"目录创建成功（或已存在）：{error_log_dir}")  # 调试信息
        except PermissionError:
            print(f"权限不足，无法创建目录：{error_log_dir}")
            sys.exit(1)
        except Exception as e:
            print(f"创建目录失败：{str(e)}，路径：{error_log_dir}")
            sys.exit(1)
        
        # 4. 验证目录是否真的存在（双重保险）
        if not os.path.exists(error_log_dir):
            print(f"致命错误：目录创建后仍不存在！路径：{error_log_dir}")
            sys.exit(1)
        # 4. 配置logging，将ERROR级别日志写入指定文件
        logger = logging.getLogger()
        # 避免重复添加处理器（防止日志重复写入）
        has_target_handler = any(
            isinstance(handler, logging.FileHandler) and handler.baseFilename == error_log_file
            for handler in logger.handlers
        )
        if not has_target_handler:
            # 创建文件处理器，指定日志文件和编码
            file_handler = logging.FileHandler(error_log_file, mode='a', encoding='utf-8')
            file_handler.setLevel(logging.ERROR)  # 只处理ERROR级别日志
            # 设置日志格式（仅保留消息内容，与原输出一致）
            formatter = logging.Formatter('%(message)s')
            file_handler.setFormatter(formatter)
            logger.addHandler(file_handler)

        logging.error("Compare failed!")
        # 打印所有数据，错误的标error，正确的标info
        for i in range(len(rel_ok)):  # 遍历所有索引
            real = real_result[i]
            gold = golden[i]
            diff = abs_diff[i]
            x_val = input_x[i]
            y_val = input_y[i]
            if not rel_ok[i]:  # 错误数据：用error级别
                logging.error(f"idx={i}, x={x_val}, y={y_val}, real={real}, golden={gold}, diff={diff}")

            else:  # 正确数据：用info级别
                logging.info(f"idx={i}, x={x_val}, y={y_val}, real={real}, golden={gold}, diff={diff}")
        sys.exit(1)

    tag = True
    for i in range(len(rel_ok)):  # 遍历所有索引
            real = real_result[i]
            gold = golden[i]
            diff = abs_diff[i]
            if not rel_ok[i]:  # 错误数据：用error级别
                logging.error(f"idx={i}, real={real}, golden={gold}, diff={diff}")
                tag = False
            else:  # 正确数据：用info级别
                logging.info(f"idx={i}, real={real}, golden={gold}, diff={diff}")

    
    if tag == True :
        logging.info("✅ test pass")
    else :
        logging.error("✅ test fail")
    sys.exit(0)


if __name__ == '__main__':
    if len(sys.argv) < 3:
        print(f"Usage: {sys.argv[0]} <real_result.bin> <golden.bin>")
        sys.exit(1)

    verify_result(sys.argv[1], sys.argv[2])
